from time import time
import numpy as np
from python_lib.instruments.daq import daqDevice, DaqError

from python_lib.instruments.daqh import DaamNShot, DaamInfinitePost, \
                DgainX1, DafBipolar, DafDifferential, \
                DarmFrequency, DarmPeriod, DaasPostTrig, \
                DatmDriverBuf, \
                DatmUpdateSingle, DatmCycleOff, \
                DatmUpdateSingle, DatmCycleOn, \
                DabtmOldest, DabtmWait, \
                DatsHardwareAnalog, DetsAboveLevel, DaqTypeAnalogLocal, \
                DaqStartEvent, DatsImmediate, \
                DatsScanCount, DaqStopEvent, \
                DteAdcDone, DaafAcqActive, DacsAdcClock, \
                DafSettle1us, DetsRisingEdge

class daqImpact(daqDevice,object):
    """
    Class to setups and collect data from an impact measurement using the
    OMB-DAQ-3005.  This class depends on the daq.py and daqh.py modules, which
    are python wrappers for the daq API C files daq.cc and daq.h, respectively,
    using the ctypes module. Details of the API are in the OMB-Programmer's
    Manual Adobe Acrobat file.  In particular, see 4.1-4 (page 48) for
    parameter definitions and 4.1-8 (page 52) for a list of API functions (not
    necessarily included in the Python wrapper, and 4.7-1 (page 319) for error
    codes."
    """
    def __init__(self):
        """
        Define class variables
        params
            scans       number of scans after trigger
            channels    list of channels to scan
            gains       list of gain settings (same length as channels)
            flags       flags for definind the scans
            frequency   data collection frequency
        buffer data
        """
        super(daqImpact,self).__init__('PersonalDaq3005')
        self.params = {
                       'scans':1000,
                       'buffer_size':100000,
                       'channels':[0],                          
                       'gains':[DgainX1],                       
                       'flags':[DafBipolar|DafDifferential|DafSettle1us],     
                       'frequency': 1.0e6                       
                       }
        self.buffer_data = []       # ADC buffer

    def set_parameters(self,params):
        """
        Set the parameters
        """
        self.params.update(params)

    def setup(self):
        """
        Setup the DAC parameters
        """
        self.set_acquisition()
        self.set_scans()
        self.set_trigger()
        self.set_clock()
        actual_rate = self.set_rate()
        self.buffer = self.set_buffer()

    def set_acquisition(self):
        """
        Set the aquisition (see 4.2-20 (page 76))
        """
        acqmode = DaamInfinitePost
        self.AdcSetAcq(acqmode)

    def set_scans(self):
        """
        Set the scans (see 4.2-36 (page 92))
        """
        self.AdcSetScan(self.params['channels'], self.params['gains'], self.params['flags']) 

    def set_buffer(self):
        """
        Set the buffer (see 4.2-62 (page 118))
        """
        transMask = DatmUpdateSingle|DatmDriverBuf|DatmCycleOn
        size = self.params['buffer_size'] * len(self.params['channels'])
        self.buffer = self.AdcTransferSetBuffer(transMask, size) 

    def set_trigger(self):
        """
        Set the trigger (see 4.6-23 (page 302))
        """
        #        self.params['gains'][0], self.params['flags'][0], \
        #        DaqTypeAnalogLocal, 0., 0., DaqStartEvent) 
        self.SetTriggerEvent(DatsImmediate, None, self.params['channels'][0], \
                self.params['gains'][0], self.params['flags'][0], \
                DaqTypeAnalogLocal, 0., 0., DaqStartEvent) 

    def set_clock(self):
        """
        Set the clock source to the internal ADC clock.
        """
        self.AdcSetClockSource(DacsAdcClock)

    def set_rate(self):
        """
        Set the rate (see 4.2-32 (page 88))
        """
        state = DaasPostTrig
        mode = DarmFrequency
        freq = self.params['frequency']
        actualRate = self.AdcSetRate(mode, state, freq)
        return actualRate

    def perform_acquisition(self):
        """
        Start the transfer to the buffer (see 4.2-66 (page 122))
        Arm the daq acquisition (AdcArm see 4.2-1 (page 57))
        set timeout (SetTimeout see 4.6-23 (page 301))
        wait (WaitForEvent see 4.6-34 (page 312))
        check periodically (AdcTransferGetStat see 4.2-59 (page 115))
        disarm the daq (AdcDisarm see 4.2-7 (page 63))
        stop the transfer (AdcTransferStop see 4.2-67 (page 123))
        return [status (boolean True if successful, time axis (s), 
                buffer_data (numpy array voltage)]
        """
        bufTransferMask = DabtmOldest|DabtmWait
        try:
            self.AdcTransferStart()
            self.AdcArm() 
        except DaqError as err:
            if err[0] == 108:
                status = False
        else:
            daq_buffer, retCount = self.AdcTransferBufData(1000,1,bufTransferMask)
            daq_buffer = np.array(daq_buffer)
            threshold = np.mean(daq_buffer) + 100*np.std(daq_buffer)
            daq_buffer, retCount = self.AdcTransferBufData(1000,1,bufTransferMask)
            start_time = time()
            while max(daq_buffer) < threshold and time() < start_time + 5:
                pretrig_buffer = daq_buffer
                daq_buffer, retCount = self.AdcTransferBufData(1000,1,bufTransferMask)
            posttrig_buffer, retCount = self.AdcTransferBufData(1000,1,bufTransferMask)
            if time() < start_time + 5:
                status = True
            else:
                status = False
        finally:
            self.AdcDisarm()
            self.AdcTransferStop() 
        buffer_data = np.array(pretrig_buffer + daq_buffer + posttrig_buffer)
        [time_axis, buffer_data] = self.get_data(buffer_data)
        return [status, time_axis, buffer_data]

    def get_data(self,buffer_data):
        """
        Take raw buffer data, crop the data such that the maximum
        occurs at 100us and is a length of 1000us. 
        Calculate a time axis that corresponds to the acquisition parameters
        return list numpy arrays [time_axis (s),voltage (mv)]
        """
        end_time = float(self.params['scans']) / float(self.params['frequency'])
        period = 1. / float(self.params['frequency'])
        time_axis = np.linspace(0,end_time,self.params['scans'],period)
        
        maximum = np.argmax(buffer_data)
        buffer_data = buffer_data[maximum-100:maximum+900]
        return [time_axis, buffer_data]

    def close(self):
        """
        Close the device conenction
        """
        self.Close()

if __name__=='__main__':
    daq = daqImpact()
